package cn.xshi.iot.common;

import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.*;
import com.serotonin.modbus4j.serial.SerialPortWrapper;
import cn.xshi.common.util.ExceptionUtil;
import cn.xshi.common.util.StringUtil;
import cn.xshi.iot.common.io.SerialPortWrapperImpl;
import cn.xshi.iot.common.vo.ModBus4jConstant;
import cn.xshi.iot.common.vo.ModBus4jEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.util.List;

/**
 * @Desc ModBus4j实体
 * @Author 邓纯杰
 * @CreateTime 2012-12-12 12:12:12
 */
@Slf4j
public class ModBus4jUtil {
    /**
     * 工厂。
     */
    static ModbusFactory modbusFactory;

    static {
        if (modbusFactory == null) {
            modbusFactory = new ModbusFactory();
        }
    }

    /**
     * 获取master
     *
     * @return
     * @throws ModbusInitException
     */
    public static ModbusMaster getMaster(ModBus4jEntity modBus4jEntity) throws ModbusInitException {
        ModbusMaster master = null;//创建主站
        SerialPortWrapper wrapper = null;//构造RTU及ASCII参数
        IpParameters params = new IpParameters();//构造ＴＣＰ及ＵＤＰ
        params.setHost(modBus4jEntity.getIp());
        params.setPort(modBus4jEntity.getPort());
        String protocol =  modBus4jEntity.getProtocol();
        if(StringUtil.isEmpty(protocol)){
            throw new ExceptionUtil("通信协议不能为空");
        }
        switch (protocol){
            case ModBus4jConstant.RTU:
                wrapper =  new SerialPortWrapperImpl(modBus4jEntity.getCommPortId(), modBus4jEntity.getBaudRate(), modBus4jEntity.getFlowControlIn(), modBus4jEntity.getFlowControlOut(), modBus4jEntity.getDataBits(), modBus4jEntity.getStopBits(), modBus4jEntity.getParity());
                master = modbusFactory.createRtuMaster(wrapper); //RTU 协议
                break;
            case ModBus4jConstant.UDP:
                master = modbusFactory.createUdpMaster(params);//UDP 协议
                break;
            case ModBus4jConstant.ASCII:
                wrapper =  new SerialPortWrapperImpl(modBus4jEntity.getCommPortId(), modBus4jEntity.getBaudRate(), modBus4jEntity.getFlowControlIn(), modBus4jEntity.getFlowControlOut(), modBus4jEntity.getDataBits(), modBus4jEntity.getStopBits(), modBus4jEntity.getParity());
                master = modbusFactory.createAsciiMaster(wrapper);//ASCII 协议
                break;
            case ModBus4jConstant.TCP:
                master = modbusFactory.createTcpMaster(params, false);// TCP 协议
                break;
            default:
                master = modbusFactory.createTcpMaster(params, false);// TCP 协议（默认TCP）
                break;
        }
        if(null != modBus4jEntity.getTimeout()){
            master.setTimeout(modBus4jEntity.getTimeout());//设置超时时间
        }
       if(null != modBus4jEntity.getRetries()){
           master.setRetries(modBus4jEntity.getRetries());//设置重试次数
       }
       master.init();
       return master;
    }


    ///////////////////////////////////////读数据开始/////////////////////////////////////////////
    /**
     * 读取[01 Coil Status 0x]类型 开关数据（读取线圈状态开关量）
     * @param modBus4jEntity
     * @return 读取值
     * @throws ModbusTransportException 异常
     * @throws ErrorResponseException  异常
     * @throws ModbusInitException 异常
     */
    public static Boolean readCoilStatus(ModBus4jEntity modBus4jEntity)
            throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // CoilStatus：读取功能代码01类型的数据，Coil：线圈，数据类型：ON表示1 OFF表示0
        BaseLocator<Boolean> loc = BaseLocator.coilStatus(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset());
        Boolean value = getMaster(modBus4jEntity).getValue(loc);
        return value;
    }

    /**
     * 读取[02 Input Status 1x]类型 开关数据（读取输入状态开关量）
     * @param modBus4jEntity
     * @return
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     * @throws ModbusInitException
     */
    public static Boolean readInputStatus(ModBus4jEntity modBus4jEntity) throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 02 Input Status
        BaseLocator<Boolean> loc = BaseLocator.inputStatus(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset());
        Boolean value = getMaster(modBus4jEntity).getValue(loc);
        return value;
    }

    /**
     * 读取[03 Holding Register类型 2x]模拟量数据（读取保持寄存器值）
     * @param modBus4jEntity modBus4jEntity
     * @return
     * @throws ModbusTransportException 异常
     * @throws ErrorResponseException  异常
     * @throws ModbusInitException 异常
     */
    public static Number readHoldingRegister(ModBus4jEntity modBus4jEntity)throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 03 Holding Register类型数据读取
        BaseLocator<Number> loc = BaseLocator.holdingRegister(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset(), modBus4jEntity.getDataType());
        Number value = getMaster(modBus4jEntity).getValue(loc);
        return value;
    }

    /**
     * 读取[04 Input Registers 3x]类型 模拟量数据（读取输入寄存器值）
     * @param modBus4jEntity modBus4jEntity
     * @return 返回结果
     * @throws ModbusTransportException 异常
     * @throws ErrorResponseException 异常
     * @throws ModbusInitException 异常
     */
    public static Number readInputRegisters(ModBus4jEntity modBus4jEntity) throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 04 Input Registers类型数据读取
        BaseLocator<Number> loc = BaseLocator.inputRegister(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset(), modBus4jEntity.getDataType());
        Number value = getMaster(modBus4jEntity).getValue(loc);
        return value;
    }

    /**
     * 批量读取使用方法
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     * @throws ModbusInitException
     */
    public static BatchResults<Number> batchRead(ModBus4jEntity modBus4jEntity) throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        BatchRead<Number> batch = new BatchRead<Number>();
        List<ModBus4jEntity> modBus4jEntityList = modBus4jEntity.getModBus4jEntityList();
        if(!CollectionUtils.isEmpty(modBus4jEntityList)){
            for(int i = 0; i < modBus4jEntityList.size(); i++){
                ModBus4jEntity entity = modBus4jEntityList.get(i);
                String registerType = entity.getRegisterType();
                switch (registerType){
                    case ModBus4jConstant.REGISTER://读取保持寄存器值
                        batch.addLocator(i, BaseLocator.holdingRegister(entity.getSlaveId(), entity.getOffset(),entity.getDataType()));
                        if(null != entity.getBit()){
                            batch.addLocator(new Float(i+"."+entity.getBit()), BaseLocator.holdingRegisterBit(entity.getSlaveId(), entity.getOffset(), entity.getBit()));//如果有BIT位则值也取出来
                        }
                        break;
                    case ModBus4jConstant.INPUT://读取输入状态开关量值
                        batch.addLocator(i, BaseLocator.inputStatus(entity.getSlaveId(), entity.getOffset()));//输入寄存器开关量没有bit位
                        break;
                    case ModBus4jConstant.COIL://读取线圈状态开关量值
                        batch.addLocator(i, BaseLocator.coilStatus(entity.getSlaveId(), entity.getOffset()));//线圈状态开关量没有bit位
                        break;
                    case ModBus4jConstant.INPUT_REGISTER://读取输入寄存器值
                        batch.addLocator(i, BaseLocator.inputRegister(entity.getSlaveId(), entity.getOffset(),entity.getDataType()));
                        if(null != entity.getBit()){
                            batch.addLocator(new Float(i+"."+entity.getBit()), BaseLocator.inputRegisterBit(entity.getSlaveId(), entity.getOffset(), entity.getBit()));//如果有BIT位则值也取出来
                        }
                        break;
                }
                batch.addLocator(i, BaseLocator.holdingRegister(entity.getSlaveId(), entity.getOffset(),entity.getDataType()));
                if(null != entity.getBit()){
                    batch.addLocator(i, BaseLocator.holdingRegister(entity.getSlaveId(), entity.getOffset(),entity.getDataType()));
                    batch.addLocator(new Float(i+"."+entity.getBit()), BaseLocator.holdingRegisterBit(entity.getSlaveId(), entity.getOffset(), entity.getBit()));//如果有BIT位则值也取出来
                }
            }
        }
        ModbusMaster master = getMaster(modBus4jEntity);
        batch.setContiguousRequests(false);
        BatchResults<Number> results = master.send(batch);
//        for(BatchResults batchResults:results){
//            log.info(""+results.getValue(0));
//        }
        return results;
    }
    ///////////////////////////////////////读数据结束/////////////////////////////////////////////



    ///////////////////////////////////////写数据开始/////////////////////////////////////////////
    /**
     * 写 [01 Coil Status(0x)]写一个 function ID = 5
     * @param modBus4jEntity modBus4jEntity
     * @return 是否写入成功
     * @throws ModbusTransportException
     * @throws ModbusInitException
     */
    public static boolean writeCoil(ModBus4jEntity modBus4jEntity) throws ModbusTransportException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster(modBus4jEntity);
        // 创建请求
        WriteCoilRequest request = new WriteCoilRequest(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset(), modBus4jEntity.getWriteCoilValue());
        // 发送请求并获取响应对象
        WriteCoilResponse response = (WriteCoilResponse) tcpMaster.send(request);
        if (response.isException()) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 写[01 Coil Status(0x)] 写多个 function ID = 15
     * @param modBus4jEntity modBus4jEntity
     * @param bdata 写入的数据
     * @return 是否写入成功
     * @throws ModbusTransportException
     * @throws ModbusInitException
     */
    public static boolean writeCoils(ModBus4jEntity modBus4jEntity, boolean[] bdata) throws ModbusTransportException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster(modBus4jEntity);
        // 创建请求
        WriteCoilsRequest request = new WriteCoilsRequest(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset(), bdata);
        // 发送请求并获取响应对象
        WriteCoilsResponse response = (WriteCoilsResponse) tcpMaster.send(request);
        if (response.isException()) {
            return false;
        } else {
            return true;
        }
    }

    /***
     * 写[03 Holding Register(4x)] 写一个 function ID = 6
     * @param modBus4jEntity modBus4jEntity
     * @return
     * @throws ModbusTransportException
     * @throws ModbusInitException
     */
    public static boolean writeRegister(ModBus4jEntity modBus4jEntity) throws ModbusTransportException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster(modBus4jEntity);
        // 创建请求对象
        WriteRegisterRequest request = new WriteRegisterRequest(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset(), modBus4jEntity.getWriteValue());
        WriteRegisterResponse response = (WriteRegisterResponse) tcpMaster.send(request);
        if (response.isException()) {
            log.error(response.getExceptionMessage());
            return false;
        } else {
            return true;
        }

    }

    /**
     *
     * 写入[03 Holding Register(4x)]写多个 function ID=16
     * @param modBus4jEntity modBus4jEntity
     * @param sdata  写入的数据
     * @return 返回是否写入成功
     * @throws ModbusTransportException
     * @throws ModbusInitException
     */
    public static boolean writeRegisters(ModBus4jEntity modBus4jEntity, short[] sdata) throws ModbusTransportException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster(modBus4jEntity);
        // 创建请求对象
        WriteRegistersRequest request = new WriteRegistersRequest(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset(), sdata);//此时的Offset表示点位的开始位置
        // 发送请求并获取响应对象
        ModbusResponse response = tcpMaster.send(request);
        if (response.isException()) {
            log.error(response.getExceptionMessage());
            return false;
        } else {
            return true;
        }
    }

    /**
     * 写入数字类型的模拟量（如:写入Float类型的模拟量、Double类型模拟量、整数类型Short、Integer、Long）
     * @param modBus4jEntity modBus4jEntity
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     * @throws ModbusInitException
     */
    public static void writeHoldingRegister(ModBus4jEntity modBus4jEntity) throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster(modBus4jEntity);
        // 类型（holdingRegister表示保持寄存器）
        BaseLocator<Number> locator = BaseLocator.holdingRegister(modBus4jEntity.getSlaveId(), modBus4jEntity.getOffset(), modBus4jEntity.getDataType());
        tcpMaster.setValue(locator, modBus4jEntity.getValue());//写入值,Number的子类,例如写入Float浮点类型,Double双精度类型,以及整型short,int,long
    }
    ///////////////////////////////////////写数据结束/////////////////////////////////////////////




    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) {
        ////////////////测试读数据开始///////////////////////
        try {
            // 01测试
            ModBus4jEntity modBus4jEntity = new ModBus4jEntity();
            modBus4jEntity.setIp("192.168.87.207");
            modBus4jEntity.setPort(503);
            modBus4jEntity.setProtocol(ModBus4jConstant.TCP);
            modBus4jEntity.setSlaveId(1);
            modBus4jEntity.setOffset(1);
            Boolean v011 = readCoilStatus(modBus4jEntity);
            log.info("v011------"+v011);

            // 02测试
//            modBus4jEntity = new ModBus4jEntity();
//            modBus4jEntity.setProtocol(ModBus4jConstant.TCP);
//            modBus4jEntity.setIp("192.168.87.207");
//            modBus4jEntity.setPort(503);
//            modBus4jEntity.setSlaveId(1);
//            modBus4jEntity.setOffset(1);
//            Boolean v021 = readInputStatus(modBus4jEntity);
//            log.info("v021------"+v021);

            // 03测试
            modBus4jEntity = new ModBus4jEntity();
            modBus4jEntity.setProtocol(ModBus4jConstant.TCP);
            modBus4jEntity.setIp("192.168.89.251");
            modBus4jEntity.setPort(9600);
            modBus4jEntity.setSlaveId(1);
            modBus4jEntity.setOffset(1);
            modBus4jEntity.setDataType(DataType.FOUR_BYTE_FLOAT);// float
            Number v031 = readHoldingRegister(modBus4jEntity);
            System.out.println("v031:" + v031);
            log.info("v031------"+v031);

//            // 04测试
//            modBus4jEntity = new ModBus4jEntity();
//            modBus4jEntity.setIp("192.168.89.251");
//            modBus4jEntity.setPort(9600);
//            modBus4jEntity.setSlaveId(1);
//            modBus4jEntity.setOffset(1);
//            modBus4jEntity.setDataType(DataType.FOUR_BYTE_FLOAT);// float
//            Number v041 = readInputRegisters(modBus4jEntity);
//            System.out.println("v041:" + v041);
//            log.info("v041------"+v041);

//
//            // 批量读取
//            modBus4jEntity = new ModBus4jEntity();
//            modBus4jEntity.setIp("192.168.89.251");
//            modBus4jEntity.setPort(9600);
//            modBus4jEntity.setSlaveId(1);
//            modBus4jEntity.setOffset(1);
//            modBus4jEntity.setDataType(DataType.FOUR_BYTE_FLOAT);// float
//            batchRead(modBus4jEntity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        ////////////////测试读数据结束///////////////////////



        ////////////////测试写数据开始///////////////////////
        try {
            //@formatter:off
            // 测试01
//			boolean t01 = writeCoil(1, 0, true);
//			System.out.println("T01:" + t01);

            // 测试02
//			boolean t02 = writeCoils(1, 0, new boolean[] { true, false, true });
//			System.out.println("T02:" + t02);

            // 测试03
//			short v = -3;
//			boolean t03 = writeRegister(1, 0, v);
//			System.out.println("T03:" + t03);
            // 测试04
//			boolean t04 = writeRegisters(1, 0, new short[] { -3, 3, 9 });
//			System.out.println("t04:" + t04);


//            //写模拟量
//            ModBus4jEntity modBus4jEntity = new ModBus4jEntity();
//            modBus4jEntity.setIp("192.168.87.207");
//            modBus4jEntity.setProtocol(ModBus4jConstant.TCP);
//            modBus4jEntity.setPort(503);
//            modBus4jEntity.setSlaveId(1);
//            modBus4jEntity.setOffset(1);
//            modBus4jEntity.setValue(10.1f);
//            modBus4jEntity.setDataType(DataType.FOUR_BYTE_FLOAT);// float
//            writeHoldingRegister(modBus4jEntity);
            //@formatter:on
        } catch (Exception e) {
            e.printStackTrace();
        }
        ////////////////测试写数据结束///////////////////////
    }
}
